#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <cstl/common.h>
#include <cstl/memory_pool.h>

/**
* Encapsulate of default malloc and free for detecting memory leak.
* @{
*/
int GMemleaknum = 0;  // memory leak nums

void* __sys_malloc(unsigned int size)
{
    void* ret = 0;
    ret = malloc(size);
    if (ret)
    {
        GMemleaknum++;
    }
    return ret;
}

void* __sys_calloc(unsigned int size)
{
    void* ret = __sys_malloc(size);

    if (0 != ret)
    {
        memset(ret, 0, size);
    }
    return ret;
}

void __sys_free(void* mem)
{
    if (mem)
    {
        free(mem);
        GMemleaknum--;
        // printf("after free %d\n",GMemleaknum);
    }
}

int __sys_leak_num(void)
{
    return GMemleaknum;
}
/* @} */

/**
* Memory block list used to store free memory.
* @{
*/
/**
* Union used to store the memory block applied from OS.
*/
typedef union memblock
{
    union memblock* next;  // point to the next dynmic allocated memory.
    uint8 data[1];  // memory content
} memblock_t;

/**
* List used to store union memblock_t
*/
typedef struct memblock_list
{
    memblock_t* first;
    int blocknums;
    uint64 blocksize;
} memblock_list_t;

void* memblocklist_init(memblock_list_t* list, uint64 blocksize)
{
    list->blocknums = 0;
    list->first = 0;
    list->blocksize = blocksize;
}

void* memblocklist_pop(memblock_list_t* list)
{
    memblock_t* block = 0;
    block = list->first;
    if (block)
    {
        list->first = block->next;
        list->blocknums -= 1;
    }
    // printf("try pop memory from block with size %d, %d\n", list->blocksize, list->blocknums);
    return ((void*)block);
}

void memblocklist_push(memblock_list_t * list, void * mem, uint32 size)
{
    memblock_t* block;
    if (size != list->blocksize)
    {
        assert(size >= list->blocksize);
    }

    block = (memblock_t*)mem;
    block->next = list->first;
    list->first = block;
    list->blocknums += 1;

    // printf("push mem %x , size %d into block with size %d, %d\n", mem, size, list->blocksize, list->blocknums);
}

void memblocklist_destroy(memblock_list_t * list)
{
    memblock_t* cur = 0;
    cur = memblocklist_pop(list);

    while(cur)
    {
        __sys_free(cur);
        cur = memblocklist_pop(list);
    }
}

/* @} */

/**
* Memory pool implementation
* @{
*/
enum {ALIGN = 8};  // the memory allocated must be multiples of ALIGN.
enum {MAX_BYTES = 128};
enum {FREE_LIST_LEN = MAX_BYTES / ALIGN };
#define CELLING_TO_ALIGN(n) (((n) + ALIGN - 1 ) & (~ (ALIGN - 1)))
#define GET_FREELIST_INDEX(n) (((n) + ALIGN - 1) / ALIGN - 1)
#define FLOOD_TO_ALIGN(n) ((n) & (~(ALIGN - 1)))

struct mempool
{
    memblock_list_t free_list[FREE_LIST_LEN];
};

void mempool_init(mempool_t* pool)
{
    // PRINT_FUN();
    int index = 0;
    *pool = (mempool_t)__sys_malloc(sizeof(struct mempool));
    for (index = 0; index < FREE_LIST_LEN; ++index)
    {
        memblocklist_init((*pool)->free_list + index, (index + 1) * ALIGN);
    }

}

void mempool_destroy(mempool_t* pool)
{
    int index = 0;
    memblock_list_t* list = 0;
    for (index = 0; index < FREE_LIST_LEN; ++index)
    {
        list = (*pool)->free_list + index;
        memblocklist_destroy(list);
    }
    __sys_free(*pool);
}

void* mempool_malloc(mempool_t pool, size_t n)
{
    size_t upnum;
    int index;
    struct memblock_list* block;
    void* tmp;
    if (n > (size_t)MAX_BYTES)
    {
        tmp = __sys_malloc(n);
    }
    else
    {
        upnum = CELLING_TO_ALIGN(n);
        index = GET_FREELIST_INDEX(n);
        // printf("malloc: upnum %d index %d\n", upnum, index);
        block = pool->free_list + index;
        if (0 == block->first)
        {
            for (index = 0; index < 10; ++index)
            {
                tmp = __sys_malloc(upnum);
                memblocklist_push(block, tmp, upnum);
            }
        }
        tmp = memblocklist_pop(block);
    }

    return tmp;
}

void mempool_free(mempool_t pool, void* mem, size_t n)
{
    size_t upnum;
    int index;
    struct memblock_list* block;

    if (n > (size_t)MAX_BYTES || n < ALIGN)
    {
        __sys_free(mem);
    }
    else
    {
        // upnum = CELLING_TO_ALIGN(n);
        upnum = FLOOD_TO_ALIGN(n);
        // upnum = n % ALIGN == 0 ? n : ALIGN;
        index = GET_FREELIST_INDEX(upnum);
        block = pool->free_list + index;
        // printf("free: size %d upnum %d index %d\n", n, upnum, index);
        memblocklist_push(block, mem, upnum);
    }
}
/* @} */

